#region Released to Public Domain by Gael Fraiteur

/*----------------------------------------------------------------------------*
 *   This file is part of samples of PostSharp.                                *
 *                                                                             *
 *   This sample is free software: you have an unlimited right to              *
 *   redistribute it and/or modify it.                                         *
 *                                                                             *
 *   This sample is distributed in the hope that it will be useful,            *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of            *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                      *
 *                                                                             *
 *----------------------------------------------------------------------------*/

#endregion

namespace ScrumSprintMonitor.UI.Wpf.PostSharp
{
	#region #using Directives

	using System;
	using System.ComponentModel;

	using global::PostSharp.Extensibility;
	using global::PostSharp.Laos;

	#endregion

	/// <summary>
	/// Custom attribute that, when applied on a type (designated <i>target type</i>), implements the interface
	/// <see cref="INotifyPropertyChanged"/>.
	/// </summary>
	/// <remarks>
	/// Event raising is implemented by appending logic to the <b>set</b> accessor of properties. The 
	/// <see cref="INotifyPropertyChanged.PropertyChanged"/> is raised only when accessors successfully complete.
	/// </remarks>
	[MulticastAttributeUsage(MulticastTargets.Class | MulticastTargets.Struct)]
	[Serializable]
	public sealed class NotifyPropertyChangedAttribute : CompoundAspect
	{
		/// <summary>
		/// Method called at compile time to get individual aspects required by the current compound
		/// aspect.
		/// </summary>
		/// <param name="targetElement">Metadata element (<see cref="Type"/> in our case) to which
		/// the current custom attribute instance is applied.</param>
		/// <param name="collection">Collection of aspects to which individual aspects should be
		/// added.</param>
		public override void ProvideAspects(object targetElement, LaosReflectionAspectCollection collection)
		{
			// Get the target type.
			var targetType = (Type) targetElement;

			// On the type, add a Composition aspect to implement the INotifyPropertyChanged interface.
			collection.AddAspect(targetType, new AddNotifyPropertyChangedInterfaceSubAspect());
		}

		#region Nested type: AddNotifyPropertyChangedInterfaceSubAspect

		/// <summary>
		/// Implementation of <see cref="CompositionAspect"/> that adds the <see cref="INotifyPropertyChanged"/>
		/// interface to the type to which it is applied.
		/// </summary>
		[Serializable]
		internal class AddNotifyPropertyChangedInterfaceSubAspect : CompositionAspect
		{
			/// <summary>
			/// Called at runtime, creates the implementation of the <see cref="INotifyPropertyChanged"/> interface.
			/// </summary>
			/// <param name="eventArgs">Execution context.</param>
			/// <returns>A new instance of <see cref="NotifyPropertyChangedImplementation"/>, which implements
			/// <see cref="INotifyPropertyChanged"/>.</returns>
			public override object CreateImplementationObject(InstanceBoundLaosEventArgs eventArgs)
			{
				return new NotifyPropertyChangedImplementation(eventArgs.Instance);
			}

			/// <summary>
			/// Called at compile-time, gets the interface that should be publicly exposed.
			/// </summary>
			/// <param name="containerType">Type on which the interface will be implemented.</param>
			/// <returns></returns>
			public override Type GetPublicInterface(Type containerType)
			{
				return typeof(INotifyPropertyChanged);
			}

			public override Type[] GetProtectedInterfaces(Type containerType)
			{
				return new[] {typeof(IFirePropertyChanged)};
			}

			/// <summary>
			/// Gets weaving options.
			/// </summary>
			/// <returns>Weaving options specifying that the implementation accessor interface (<see cref="IComposed{T}"/>)
			/// should be exposed, and that the implementation of interfaces should be silently ignored if they are
			/// already implemented in the parent types.</returns>
			public override CompositionAspectOptions GetOptions()
			{
				return CompositionAspectOptions.GenerateImplementationAccessor | CompositionAspectOptions.IgnoreIfAlreadyImplemented;
			}
		}

		#endregion

		#region Nested type: NotifyPropertyChangedImplementation

		/// <summary>
		/// Implementation of the <see cref="INotifyPropertyChanged"/> interface.
		/// </summary>
		internal class NotifyPropertyChangedImplementation : INotifyPropertyChanged,
		                                                     IFirePropertyChanged
		{
			// Instance that exposes the current implementation.
			private readonly object instance;

			/// <summary>
			/// Initializes a new <see cref="NotifyPropertyChangedImplementation"/> instance.
			/// </summary>
			/// <param name="instance">Instance that exposes the current implementation.</param>
			public NotifyPropertyChangedImplementation(object instance)
			{
				this.instance = instance;
			}

			#region IFirePropertyChanged Members

			/// <summary>
			/// Raises the <see cref="PropertyChanged"/> event. Called by the
			/// property-level aspect (<see cref="AddNotifyPropertyChangedInterfaceSubAspect"/>)
			/// at the end of property set accessors.
			/// </summary>
			/// <param name="propertyName">Name of the changed property.</param>
			public void OnPropertyChanged(string propertyName)
			{
				if (PropertyChanged != null)
				{
					PropertyChanged(this.instance, new PropertyChangedEventArgs(propertyName));
				}
			}

			#endregion

			#region INotifyPropertyChanged Members

			/// <summary>
			/// Event raised when a property is changed on the instance that
			/// exposes the current implementation.
			/// </summary>
			public event PropertyChangedEventHandler PropertyChanged;

			#endregion
		}

		#endregion
	}
}